View Javadoc

1   package org.apache.maven.surefire.junitcore.pc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.junitcore.JUnitCoreParameters;
23  import org.apache.maven.surefire.testset.TestSetFailedException;
24  
25  /**
26   * An algorithm which configures {@link ParallelComputer} with allocated thread resources by given
27   * {@link org.apache.maven.surefire.junitcore.JUnitCoreParameters}.
28   * The <code>AbstractSurefireMojo</code> has to provide correct combinations of thread-counts and <em>parallel</em>.
29   *
30   * @author Tibor Digana (tibor17)
31   * @see org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilder
32   * @since 2.16
33   */
34  final class ParallelComputerUtil
35  {
36      private static int availableProcessors = Runtime.getRuntime().availableProcessors();
37  
38      private ParallelComputerUtil()
39      {
40          throw new IllegalStateException( "Suppresses calling constructor, ensuring non-instantiability." );
41      }
42  
43      /*
44      * For testing purposes.
45      */
46      static void overrideAvailableProcessors( int availableProcessors )
47      {
48          ParallelComputerUtil.availableProcessors = availableProcessors;
49      }
50  
51      /*
52      * For testing purposes.
53      */
54      static void setDefaultAvailableProcessors()
55      {
56          ParallelComputerUtil.availableProcessors = Runtime.getRuntime().availableProcessors();
57      }
58  
59      static Concurrency resolveConcurrency( JUnitCoreParameters params, RunnerCounter counts )
60          throws TestSetFailedException
61      {
62          if ( !params.isParallelismSelected() )
63          {
64              throw new TestSetFailedException( "Unspecified parameter '" + JUnitCoreParameters.PARALLEL_KEY + "'." );
65          }
66  
67          if ( !params.isUseUnlimitedThreads() && !hasThreadCount( params ) && !hasThreadCounts( params ) )
68          {
69              throw new TestSetFailedException( "Unspecified thread-count(s). " +
70                                                    "See the parameters " + JUnitCoreParameters.USEUNLIMITEDTHREADS_KEY
71                                                    + ", " + JUnitCoreParameters.THREADCOUNT_KEY + ", "
72                                                    + JUnitCoreParameters.THREADCOUNTSUITES_KEY + ", "
73                                                    + JUnitCoreParameters.THREADCOUNTCLASSES_KEY + ", "
74                                                    + JUnitCoreParameters.THREADCOUNTMETHODS_KEY + "." );
75          }
76  
77          if ( params.isUseUnlimitedThreads() )
78          {
79              return concurrencyForUnlimitedThreads( params );
80          }
81          else if ( hasThreadCount( params ) )
82          {
83              if ( hasThreadCounts( params ) )
84              {
85                  return isLeafUnspecified( params )
86                      ? concurrencyFromAllThreadCountsButUnspecifiedLeafCount( params, counts )
87                      : concurrencyFromAllThreadCounts( params );
88              }
89              else
90              {
91                  return estimateConcurrency( params, counts );
92              }
93          }
94          else
95          {
96              return concurrencyFromThreadCounts( params );
97          }
98      }
99  
100     private static Concurrency concurrencyForUnlimitedThreads( JUnitCoreParameters params )
101     {
102         Concurrency concurrency = new Concurrency();
103         concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
104         concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
105         concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0;
106         concurrency.capacity = Integer.MAX_VALUE;
107         return concurrency;
108     }
109 
110     private static Concurrency estimateConcurrency( JUnitCoreParameters params, RunnerCounter counts )
111     {
112         final Concurrency concurrency = new Concurrency();
113         final int parallelEntities = countParallelEntities( params );
114         concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() );
115         if ( parallelEntities == 1 || counts == null || counts.classes == 0 )
116         {
117             // Estimate parallel thread counts.
118             double ratio = 1d / parallelEntities;
119             int threads = multiplyByCoreCount( params, ratio * params.getThreadCount() );
120             concurrency.suites = params.isParallelSuites() ? minSuites( threads, counts ) : 0;
121             concurrency.classes = params.isParallelClasses() ? minClasses( threads, counts ) : 0;
122             concurrency.methods = params.isParallelMethods() ? minMethods( threads, counts ) : 0;
123             if ( parallelEntities == 1 )
124             {
125                 concurrency.capacity = 0;
126             }
127             else
128             {
129                 adjustLeaf( params, concurrency );
130             }
131         }
132         else
133         {
134             // Try to allocate suites+classes+methods within threadCount,
135             concurrency.suites = params.isParallelSuites() ? toNonNegative( counts.suites ) : 0;
136             concurrency.classes = params.isParallelClasses() ? toNonNegative( counts.classes ) : 0;
137             concurrency.methods =
138                 params.isParallelMethods() ? toNonNegative( Math.ceil( counts.methods / (double) counts.classes ) ) : 0;
139             double sum = toNonNegative( concurrency.suites + concurrency.classes + concurrency.methods );
140             if ( concurrency.capacity < sum && sum != 0 )
141             {
142                 // otherwise allocate them using the weighting factor < 1.
143                 double weight = concurrency.capacity / sum;
144                 concurrency.suites *= weight;
145                 concurrency.classes *= weight;
146                 concurrency.methods *= weight;
147             }
148             adjustLeaf( params, concurrency );
149         }
150         return concurrency;
151     }
152 
153     private static Concurrency concurrencyFromAllThreadCountsButUnspecifiedLeafCount( JUnitCoreParameters params,
154                                                                                       RunnerCounter counts )
155     {
156         Concurrency concurrency = new Concurrency();
157         concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
158         concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.suites ) : 0;
159         concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
160         concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.classes ) : 0;
161         concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0;
162         concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.methods ) : 0;
163         concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() );
164 
165         if ( counts != null )
166         {
167             concurrency.suites = toNonNegative( Math.min( concurrency.suites, counts.suites ) );
168             concurrency.classes = toNonNegative( Math.min( concurrency.classes, counts.classes ) );
169         }
170 
171         setLeafInfinite( params, concurrency );
172 
173         return concurrency;
174     }
175 
176     private static Concurrency concurrencyFromAllThreadCounts( JUnitCoreParameters params )
177     {
178         Concurrency concurrency = new Concurrency();
179         concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
180         concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
181         concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0;
182         concurrency.capacity = params.getThreadCount();
183         double all = sumThreadCounts( concurrency );
184 
185         concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.capacity * (
186             concurrency.suites / all ) ) : 0;
187 
188         concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.capacity * (
189             concurrency.classes / all ) ) : 0;
190 
191         concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.capacity * (
192             concurrency.methods / all ) ) : 0;
193 
194         concurrency.capacity = multiplyByCoreCount( params, concurrency.capacity );
195         adjustPrecisionInLeaf( params, concurrency );
196         return concurrency;
197     }
198 
199     private static Concurrency concurrencyFromThreadCounts( JUnitCoreParameters params )
200     {
201         Concurrency concurrency = new Concurrency();
202         concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
203         concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
204         concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0;
205         concurrency.capacity = toNonNegative( sumThreadCounts( concurrency ) );
206         return concurrency;
207     }
208 
209     private static int countParallelEntities( JUnitCoreParameters params )
210     {
211         int count = 0;
212         if ( params.isParallelSuites() )
213         {
214             count++;
215         }
216 
217         if ( params.isParallelClasses() )
218         {
219             count++;
220         }
221 
222         if ( params.isParallelMethods() )
223         {
224             count++;
225         }
226         return count;
227     }
228 
229     private static void adjustPrecisionInLeaf( JUnitCoreParameters params, Concurrency concurrency )
230     {
231         if ( params.isParallelMethods() )
232         {
233             concurrency.methods = concurrency.capacity - concurrency.suites - concurrency.classes;
234         }
235         else if ( params.isParallelClasses() )
236         {
237             concurrency.classes = concurrency.capacity - concurrency.suites;
238         }
239     }
240 
241     private static void adjustLeaf( JUnitCoreParameters params, Concurrency concurrency )
242     {
243         if ( params.isParallelMethods() )
244         {
245             concurrency.methods = Integer.MAX_VALUE;
246         }
247         else if ( params.isParallelClasses() )
248         {
249             concurrency.classes = Integer.MAX_VALUE;
250         }
251     }
252 
253     private static void setLeafInfinite( JUnitCoreParameters params, Concurrency concurrency )
254     {
255         if ( params.isParallelMethods() )
256         {
257             concurrency.methods = Integer.MAX_VALUE;
258         }
259         else if ( params.isParallelClasses() )
260         {
261             concurrency.classes = Integer.MAX_VALUE;
262         }
263         else if ( params.isParallelSuites() )
264         {
265             concurrency.suites = Integer.MAX_VALUE;
266         }
267     }
268 
269     private static boolean isLeafUnspecified( JUnitCoreParameters params )
270     {
271         int maskOfParallel = params.isParallelSuites() ? 4 : 0;
272         maskOfParallel |= params.isParallelClasses() ? 2 : 0;
273         maskOfParallel |= params.isParallelMethods() ? 1 : 0;
274 
275         int maskOfConcurrency = params.getThreadCountSuites() > 0 ? 4 : 0;
276         maskOfConcurrency |= params.getThreadCountClasses() > 0 ? 2 : 0;
277         maskOfConcurrency |= params.getThreadCountMethods() > 0 ? 1 : 0;
278 
279         maskOfConcurrency &= maskOfParallel;
280 
281         int leaf = Integer.lowestOneBit( maskOfParallel );
282         return maskOfConcurrency == maskOfParallel - leaf;
283     }
284 
285     private static double sumThreadCounts( Concurrency concurrency )
286     {
287         double sum = concurrency.suites;
288         sum += concurrency.classes;
289         sum += concurrency.methods;
290         return sum;
291     }
292 
293     private static boolean hasThreadCounts( JUnitCoreParameters jUnitCoreParameters )
294     {
295         return jUnitCoreParameters.isParallelSuites() && jUnitCoreParameters.getThreadCountSuites() > 0 ||
296             jUnitCoreParameters.isParallelClasses() && jUnitCoreParameters.getThreadCountClasses() > 0 ||
297             jUnitCoreParameters.isParallelMethods() && jUnitCoreParameters.getThreadCountMethods() > 0;
298     }
299 
300     private static boolean hasThreadCount( JUnitCoreParameters jUnitCoreParameters )
301     {
302         return jUnitCoreParameters.getThreadCount() > 0;
303     }
304 
305     private static int threadCountMethods( JUnitCoreParameters jUnitCoreParameters )
306     {
307         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountMethods() );
308     }
309 
310     private static int threadCountClasses( JUnitCoreParameters jUnitCoreParameters )
311     {
312         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountClasses() );
313     }
314 
315     private static int threadCountSuites( JUnitCoreParameters jUnitCoreParameters )
316     {
317         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountSuites() );
318     }
319 
320     private static int multiplyByCoreCount( JUnitCoreParameters jUnitCoreParameters, double threadsPerCore )
321     {
322         double numberOfThreads =
323             jUnitCoreParameters.isPerCoreThreadCount() ? threadsPerCore * (double) availableProcessors : threadsPerCore;
324 
325         return numberOfThreads > 0 ? toNonNegative( numberOfThreads ) : Integer.MAX_VALUE;
326     }
327 
328     private static int minSuites( int threads, RunnerCounter counts )
329     {
330         long count = counts == null ? Integer.MAX_VALUE : counts.suites;
331         return Math.min( threads, toNonNegative( count ) );
332     }
333 
334     private static int minClasses( int threads, RunnerCounter counts )
335     {
336         long count = counts == null ? Integer.MAX_VALUE : counts.classes;
337         return Math.min( threads, toNonNegative( count ) );
338     }
339 
340     private static int minMethods( int threads, RunnerCounter counts )
341     {
342         long count = counts == null ? Integer.MAX_VALUE : counts.methods;
343         return Math.min( threads, toNonNegative( count ) );
344     }
345 
346     private static int toNonNegative( long num )
347     {
348         return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE );
349     }
350 
351     private static int toNonNegative( double num )
352     {
353         return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE );
354     }
355 }